/**
 *
 * @file
 *		heap_leo
 *
 * @brief
 *		Heap implementation by leo
 *		Based on 'best fit' algorithm.
 */

#include <stdlib.h>
#include <stdio.h>
#include "../../api/heap.h"
#include "heap_leo.h"

heap_leo::heap_leo( size_t total, alloc_flags flag) : heap876(total, flag, "leo") {
	heap_p = (void *) malloc(total);
	if(!heap_p)
		printf("Can't malloc!");
	else {
		alloc_sz = 0;
		first_region = (region *) malloc(sizeof(region));
		first_region->p = heap_p;
		first_region->size = total_limit = total;
		first_region->is_free = 1;
		first_region->next = NULL;
		first_region->prev = NULL;
		mutex = CreateMutex(NULL, FALSE, NULL);
	}
}

heap_leo::~heap_leo() 
{ 
	if(alloc_sz) 
		printf("\nHeap has not empty %i bytes\n", alloc_sz);
	else
		::free(heap_p);
}
void* heap_leo::alloc( size_t sz, unsigned int tag ) {
	WaitForSingleObject(mutex, INFINITE);
	if( sz > total_limit )
	{
		printf("There isn't enough space in the heap");
		return NULL;
	}
	region *reg = first_region;
	region *reg_best = NULL;
	
	while(reg) {
		if((reg->is_free)&&(reg->size >= sz)) {
			if((!reg_best)||(reg_best->size > reg->size))
				reg_best = reg;
		}
		reg = reg->next;
	}
	if(!reg_best) {
		printf("Heap is too fragmentated");
		return NULL;
	}
		
	if(reg_best->size == sz) {
		reg_best->is_free = 0;
		reg_best->tag = tag;
		alloc_sz += sz;
		total_limit -= sz;
		ReleaseMutex(mutex);
		return reg_best->p;
	} else {
		region *new_reg = (region *) malloc(sizeof(region));
	
		new_reg->p = (void *) ( (size_t)reg_best->p + sz);
		new_reg->size = reg_best->size - sz;
		new_reg->is_free = 1;
		new_reg->next = reg_best->next;
		new_reg->prev = reg_best;
	
		reg_best->size = sz;
		reg_best->is_free = 0;
		reg_best->next = new_reg;
		reg_best->tag = tag;
	
		alloc_sz += sz;
		total_limit -= sz;
		ReleaseMutex(mutex);
		return reg_best->p;
	}
}

void heap_leo::free( void* ptr ) {
	region *reg = first_region;
	region *reg_for_del;
	WaitForSingleObject(mutex, INFINITE);
	while(reg) {
		if(reg->p == ptr) {
			if(!reg->is_free) {
				alloc_sz -= reg->size;
				total_limit += reg->size;
			}
			if(reg->prev)
				if(reg->prev->is_free) {
					reg->prev->next = reg->next;
					if(reg->next)
						reg->next->prev = reg->prev;
					reg->prev->size += reg->size;
					reg_for_del = reg;
					reg = reg->prev;
					::free(reg_for_del);
				}
			if(reg->next)
				if(reg->next->is_free) {
					reg_for_del = reg->next;
					reg->size += reg->next->size;
					if(reg->next->next)
						reg->next->next->prev = reg;
					reg->next = reg->next->next;
					
					::free(reg_for_del);
				}
			reg->is_free = 1;
			ReleaseMutex(mutex);
			return;		
		}
		reg = reg->next;
	}
	printf("Can't find region of memory with such pointer");
	ReleaseMutex(mutex);
}

void  heap_leo::free_all( unsigned int tag ) {
	WaitForSingleObject(mutex, INFINITE);
	if(flag & heap876::alloc_tagged) {
		region *reg = first_region;
		region *reg_for_del;
		while(reg) {
			if((reg->tag == tag)&&(!reg->is_free)) {
				alloc_sz -= reg->size;
				total_limit += reg->size;
				if(reg->prev) 
					if(reg->prev->is_free) {
						reg->prev->next = reg->next;
						reg->prev->size += reg->size;
						reg_for_del = reg;
						reg = reg->prev;
						::free(reg_for_del);
					}
				if(reg->next) 
					if(reg->next->is_free) {
						reg->next = reg->next->next;
						reg->size += reg->next->size;
						::free(reg->next);
					}
				reg->is_free = 1;
			} 
			reg = reg->next;
		}
	} else {
		region *reg = first_region->next;
		region *reg_for_del;
		while(reg != NULL) {
			reg_for_del = reg;
			reg = reg->next;
			::free(reg_for_del);
		}
		first_region->size = total_limit = total_limit + alloc_sz;
		first_region->is_free = 1;
		first_region->next = NULL;
		alloc_sz = 0;
	}
	ReleaseMutex(mutex);
}
size_t heap_leo::get_occupancy() const {return alloc_sz;}